package ccnGame;

import java.io.IOException;
import java.sql.Timestamp;
import java.util.LinkedList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;

import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.config.UserConfiguration;
import org.ccnx.ccn.config.ConfigurationException;
import org.ccnx.ccn.impl.CCNFlowControl.SaveType;
import org.ccnx.ccn.io.content.CCNStringObject;
import org.ccnx.ccn.io.content.ContentEncodingException;
import org.ccnx.ccn.profiles.security.KeyProfile;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.KeyLocator;
import org.ccnx.ccn.protocol.PublisherPublicKeyDigest;
import org.ccnx.ccn.protocol.MalformedContentNameStringException;


public class CCNGameNet {
	public interface CCNGameCallBack{
		public void recvMessage(Message msg);
	}
	
	private final ContentName _namespace;
	private final String _namespaceStr;
	private final CCNGameCallBack _callback;
	private final String nick;

	private Timestamp _lastUpdate;
	private boolean _finished = false;
	private boolean _host = false;
	
	private static final long CYCLE_TIME = 1000;
	private static final int WAIT_TIME_FOR_FRIENDLY_NAME = 2500;
	
	private ManageList _manager = new ManageList();
	private CCNStringObject _writeString;
	private CCNStringObject _readString;
	
	private CCNStringObject _readNameString;
	private CCNStringObject _writeNameString;
	
	private HashMap<PublisherPublicKeyDigest, String> _friendlyNameToDigestHash;
	//private UserList _userList;
	private LinkedList<String> _userList;

	public CCNGameNet(CCNGameCallBack callback, String uri, String NickName) throws MalformedContentNameStringException{
		_namespace = ContentName.fromURI(uri);
		_namespaceStr = uri;
		_callback = callback;
		nick = NickName;
		_friendlyNameToDigestHash = new HashMap<PublisherPublicKeyDigest, String>();
		//_userList = new UserList();
		_userList = new LinkedList<String>();
	}

	public void shutDown() throws IOException{
		if(_host && _writeNameString != null){
			if(_userList.size() == 1)
				_writeNameString.save("&&");
		}
		_finished = true;
		if(_writeString != null){
			_writeString.save("!@#$shutdown");
		}
		if(_readString != null){
			_readString.cancelInterest();
			showMessage(nick, now(), "System::::Shutting down " + _namespace + "...");
		}
		if(_readNameString != null){
			_readNameString.cancelInterest();
		}
	}
	
	public int numberOfUser(){
		return _userList.size();
	}
	
	private ContentName _friendlyNameNamespace;

	public Iterator<String> getUserList(){
		return _userList.iterator();
	}
	
	public synchronized void sendMessage(String msg) throws ContentEncodingException, IOException{
		while(!_writeString.save(msg)){
			msg = msg + " ";
		}
	}
	
	public void connect() throws MalformedContentNameStringException, IOException, ConfigurationException{
		if(_namespace.toString().startsWith("ccnx:/")){
			UserConfiguration.setDefaultNamespacePrefix(_namespace.toString().substring(5));
		}
		else{
			UserConfiguration.setDefaultNamespacePrefix(_namespace.toString());
		}
		
		//change user's nick name
		UserConfiguration.setUserName(nick);
		
		
 		CCNHandle tempReadHandle = CCNHandle.getHandle();

		// Writing must be on a different handle, to enable us to read back text we have
		// written when nobody else is reading.
		CCNHandle tempWriteHandle = CCNHandle.open();

		_readString = new CCNStringObject(_namespace, (String)null, SaveType.RAW, tempReadHandle);
		_readString.updateInBackground(true); 
		
		//announce to the others that this client joined. 
		String introduction = "Login::::" + UserConfiguration.userName() + " has entered " + _namespace;
		_writeString = new CCNStringObject(_namespace, introduction, SaveType.RAW, tempWriteHandle);
		_writeString.save();
		//_userList.addUser(nick, now());
		
		// Publish the user's friendly name under a new ContentName
		String friendlyNameNamespaceStr = _namespaceStr + "/members";
		_friendlyNameNamespace = KeyProfile.keyName(ContentName.fromURI(friendlyNameNamespaceStr), _writeString.getContentPublisher());
		
		//read the string here
		_readNameString = new CCNStringObject(_friendlyNameNamespace, (String)null, SaveType.RAW, tempReadHandle);
		//_readNameString.updateInBackground(true);
		_readNameString.update();
		if(_readNameString.isSaved()){
			if(_readNameString.string().compareTo("&&")==0){
				_host = true;
			}
			else{
				addListFromReadNameString(_readNameString.string());
			}
		}
		else{
			_host = true;
		}
		String publishedNameStr = UserConfiguration.userName();
		
		//new Thread(_manager).start();
		
		if(_host){
			_writeNameString = new CCNStringObject(_friendlyNameNamespace, publishedNameStr, SaveType.RAW, tempWriteHandle);
			_writeNameString.save();
			_userList.add(nick);
		}
		/*
		try{
			addNameToHash(_writeNameString.getContentPublisher(), _writeNameString.string());
		}
		catch(IOException e){
			e.printStackTrace();
		}
		*/
		// read string from content
		while(!_finished){ 
			try{
				synchronized(_readString){
					_readString.wait(CYCLE_TIME);
				}
			}
			catch(InterruptedException e){
			}
			
			if(_readString.isSaved()){
				Timestamp thisUpdate = _readString.getVersion();
				if((_lastUpdate == null) || thisUpdate.after(_lastUpdate)){
					_lastUpdate = thisUpdate;
					
					//lookup friendly name to display for this user
					//String userFriendlyName = getFriendlyName(_readString.getContentPublisher());
					String userFriendlyName = extractNameFromKey(_readString.getPublisherKeyLocator());
					
					showMessage(userFriendlyName, thisUpdate, _readString.string());
					
				}	
			}
		}
	}
	
	private void addListFromReadNameString(String list){
		StringTokenizer st = new StringTokenizer(list, "#");
		String t;
		while(st.hasMoreTokens()){
			t = st.nextToken();
			if(!_userList.contains(t))
			_userList.addLast(t);
			/*_userList.addUser(t, now());
			try{
				Thread.sleep(1000);
			}catch(Exception e){
				
			}*/
		}
		
		//_userList.addUser(nick, now());
		if(!_userList.contains(nick))
			_userList.addLast(nick);
	}
	
	private synchronized void sendUserList(){
		try{
			String list = new String();
			for(int i=0; i<_userList.size(); ++i){
				list = list.concat(_userList.get(i) + "##");
			}
			_writeNameString.save(list);
		}
		catch(Exception e){
			
		}
	}
	
	private String extractNameFromKey(KeyLocator key){
		StringTokenizer s = new StringTokenizer(key.toString(), "/");
		for(int i=0; i<4; ++i){
			s.nextToken();
		}
		String ret = s.nextToken();
		return ret;
	}
	
	private void addNameToHash(PublisherPublicKeyDigest digest, String friendlyName){
		_friendlyNameToDigestHash.put(digest, friendlyName);
	}
	
	private String getFriendlyName(PublisherPublicKeyDigest digest){
		if(_friendlyNameToDigestHash.containsKey(digest)){
			return _friendlyNameToDigestHash.get(digest);
		}
		else{
			return "";
		}
	}
	
	private void showMessage(String sender, Timestamp time, String message){
		if(message.compareTo("!@#$shutdown")==0){
			//_userList.removeUser(sender);
			if(sender.compareTo(_userList.getFirst())==0){
				//host disconnect
				if(nick.compareTo(_userList.get(1))==0){
					//make this client to host
					_host = true;
				}
			}
			_userList.remove(sender);
			if(_host){
				sendUserList();
			}
			else{
				
			}
			Message msg = new Message(sender, sender + "sender has shutdown.", time, Message.SHUTDOWN_MESSAGE);
			_callback.recvMessage(msg);
		}
		else if(message.startsWith("Login::::")){
			Message msg = new Message(sender, message.substring(9), time, Message.LOGIN_MESSAGE);
			//_userList.addUser(sender, time);
			if(!_userList.contains(sender)){
				_userList.addLast(sender);
			}
			if(_host){
				sendUserList();
			}
			_callback.recvMessage(msg);
		}
		else if(message.startsWith("System::::")){
			Message msg = new Message(sender, message.substring(10), time, Message.SYSTEM_MESSAGE);
			_callback.recvMessage(msg);
		}
		else{
			Message msg = new Message(sender, message, time, Message.GAME_MESSAGE);
			_callback.recvMessage(msg);
		}
	}
	
	private void showMessage(PublisherPublicKeyDigest publisher, KeyLocator keyLocator, Timestamp time, String message){
		showMessage(publisher.shortFingerprint().substring(0, 8), time, message);
	}
	
	private Timestamp now(){
		return new Timestamp(System.currentTimeMillis());
	}
	
	class ManageList implements Runnable{
		ManageList(){
			
		}
		
		public void run() {
			/*
			 * make list of users that already joined.
			 */
			while(!_finished){
				
				
				try{
					_readNameString.update(WAIT_TIME_FOR_FRIENDLY_NAME);
					if(_readNameString.available()){
						addListFromReadNameString(_readNameString.string());
					}
				}
				catch(Exception e){
					
				}
			}
		}
	}
}


class UserList implements Iterator<String>, Iterable<String> {
	private Node dummyHead;
	private int size;
	private int cur;
	
	public UserList(){
		dummyHead = new Node(null, null);
		size = 0;
	}
	
	public String toString(){
		Node p = dummyHead.getNext();
		String ret = new String();
		while(p != dummyHead && p != null){
			ret.concat(p.getName() + "##");
			p = p.getNext();
		}
		return ret;
	}
	
	public boolean hasNext(){
		return cur<size;
	}
	
	public String next(){
		return getName(cur++);
	}
	
	public void remove(){
		
	}
	
	public Iterator<String> iterator(){
		cur = 0;
		return (Iterator<String>)this;
	}
	
	public String getUserName(int order){
		//return order'th user name.
		if(order > size){
			return null;
		}
		else{
			Node ret = dummyHead;
			for(int i=0; i<order; ++i){
				ret = ret.getNext();
			}
			return ret.getName();
		}
	}
	
	public int getNumberOfUser(){
		return size;
	}
	
	/* 
	 * add user to list, order to join time
	 */
	public void addUser(String name, Timestamp time){
		Node newNode = new Node(time, name);
		Node prev, next;
		prev = next = dummyHead;
		
		for(int i=0; i < size; ++i){
			next = prev.getNext();
			Timestamp t = next.getTime();
			if(time.compareTo(t) < 0 ){
				break;
			}
			prev = next;
		}
		newNode.setNext(next);
		prev.setNext(newNode);
		++size;
	}
	
	public void removeUser(String name){
		Node prev, next;
		prev = next = dummyHead;
		for(int i=0; i<size; ++i){
			next = prev.getNext();
			if(next.getName().compareTo(name)==0){
				//delete it
				prev.setNext(next.getNext());
				break;
			}
		}
		--size;
	}
	
	public boolean isList(String name){
		Node p = dummyHead.getNext();
		while(p != dummyHead && p != null){
			if(name.compareTo(p.getName())==0){
				return true;
			}
			p = p.getNext();
		}
		return false;
	}
	
	private String getName(int order){
		Node t = dummyHead;
		if(order > size){
			return null;
		}
		else{
			for(int i=0; i<=order; ++i){
				t = t.getNext();
			}
			return t.getName();
		}
	}
}

class Node{
	private Timestamp _time;
	private String _name;
	private Node _next;
	
	public Node(Timestamp time, String name){
		_time = time;
		_name = name;
		_next = null;
	}
	
	public Timestamp getTime(){
		return _time;
	}
	
	public String getName(){
		return _name;
	}
	
	public void setNext(Node next){
		_next = next;
	}
	
	public Node getNext(){
		return _next;
	}
}